﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace DDD.Core {

    /// <summary>
    /// アタッチ可能な クラス
    /// </summary>
    /// <remarks>
    /// この抽象クラスは <see cref="Object3D"/> クラスから派生し、<see cref="Object3D"/> オブジェクトに
    /// コンポーネントをアタッチする機能を追加します。
    /// 空間上の1点に過ぎなかったオブジェクトは、種々のコンポーネントをアタッチすることで
    /// カメラやメッシュなどの機能を持つようになります。
    /// またスコープや各種Enableフラグもこのクラスが担当します（あまりいいデザインではない・・・）。
    /// <note type="implement">
    /// まだ迷っているのだが <see cref="Attachable"/> と <see cref="Node"/> クラスの継承関係を交換すべきか？
    /// 2つは独立した概念なのでどちらがどちらというものではないのだが・・・考慮中。
    /// あるいは2つのクラスを統合して1つのクラスにすべきか。少し大きすぎる気がするが一番きれいなデザイン。
    /// </note>
    /// </remarks>
    /// <seealso cref="Object3D"/>
    /// <seealso cref="Component"/>
    /// <seealso cref="Node"/>
    public class Attachable : Object3D {

        #region Field
        List<Component> components;
        int order;
        int scope;
        float alpha;
        bool renderingEnabled;
        bool pickingEnabled;
        bool collisionEnabled;
        bool lodEnabled;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 初期値は以下のとおりです。
        /// <list type="bullet">
        ///   <item><see cref="Order"/> = 0</item>
        ///   <item><see cref="Scope"/> = -1</item>
        ///   <item><see cref="Alpha"/> = 1.0</item>  
        ///   <item><see cref="IsRenderingEnabled"/> = <c>true</c></item>
        ///   <item><see cref="IsPickingEnabled"/> = <c>true</c></item>
        ///   <item><see cref="IsCollisionEnabled"/> = <c>true</c></item>
        ///   <item><see cref="IsLodEnabled"/> = <c>true</c></item>
        ///   <item><see cref="ComponentCount"/> = 0</item>
        ///   コンポーネントは登録されていません。
        /// </list>
        /// </remarks>
        public Attachable () {
            this.components = new List<Component> ();
            this.order = 0;
            this.scope = -1;
            this.alpha = 1.0f;
            this.renderingEnabled = true;
            this.pickingEnabled = true;
            this.collisionEnabled = true;
            this.lodEnabled = true;
        }
        #endregion


        #region Property

        /// <summary>
        /// 実行優先度
        /// </summary>
        /// <remarks>
        /// ノードの更新処理はこの値が小さいノードから順に実行されます。
        /// 実行優先度が同じノードの場合の順番は未定義です。
        /// デフォルトは 0 です。
        /// </remarks>
        /// <seealso cref="Component.OnUpdate()"/>
        public int ExecutionOrder {
            get { return order; }
            set { this.order = value; }
        }

        /// <summary>
        /// スコープ
        /// </summary>
        /// <remarks>
        /// スコープは <c>int</c> 型の整数値で、２つのノードのスコープのビット <c>AND</c> の計算結果が 0 でないとき
        /// 2つのノードは同じスコープにいると見なされます。
        /// これは典型的にはレンダリングやコリジョン検出で特定のグループのみを選択的に実行したいときに使用されます。
        /// デフォルト値は -1、つまり全てのスコープに含まれます。
        /// スコープの詳細については、<see cref="Attachable"/> クラスの解説を参照してください。
        /// </remarks>
        public int Scope {
            get { return scope; }
            set { this.scope = value; }
        }

        /// <summary>
        /// 不透明度(α)
        /// </summary>
        /// <remarks>
        /// ノードにはノードαと呼ばれる不透明度を設定可能です。
        /// <c>α = 0</c> は透明で、<c>α=1</c> は不透明です。
        /// なお実際に半透明で描画されるかどうかはシェーダーの作りに依存します。
        /// 実効ノードαについては <see cref="Node.EffectiveAlpha"/> を参照してください。
        /// デフォルトは、1.0 です。
        /// </remarks>
        /// <seealso cref="Node.EffectiveAlpha"/>
        public float Alpha {
            get { return alpha; }
            set {
                var alpha = value;
                if (alpha < 0 || alpha > 1) {
                    throw new ArgumentException ("Alpha must be within [0,1]");
                }
                this.alpha = alpha;
            }
        }



        /// <summary>
        /// レンダリング フラグ
        /// </summary>
        /// <remarks>
        /// レンダリング フラグはこのノードをレンダリング対象に含めるかどうかのフラグです。
        /// <c>true</c> の時レンダリングされ、<c>false</c> の時レンダリングされません(シェーダーそのものが実行されません)。
        /// 実効レンダリング フラグについては、<see cref="Node.IsEffectiveRenderingEnabled"/> を参照してください。
        /// デフォルトは <c>true</c> です。
        /// </remarks>
        /// <see cref="Node.IsEffectiveRenderingEnabled"/>
        public bool IsRenderingEnabled {
            get { return renderingEnabled; }
            set { this.renderingEnabled = value; }
        }

        /// <summary>
        /// ピッキング フラグ
        /// </summary>
        /// <remarks>
        /// ピッキング フラグはこのノードをピッキングの対象に含めるかどうかのフラグです。
        /// <c>true</c> の時ピックアップされ、<c>false</c> の時ピックアップされません。
        /// 実効ピッキング フラグについては、<see cref="Node.IsEffectivePickingEnabled"/> を参照してください。
        /// デフォルトは <c>true</c> です。
        /// </remarks>
        /// <see cref="Node.IsEffectivePickingEnabled"/>
        public bool IsPickingEnabled {
            get { return pickingEnabled; }
            set { this.pickingEnabled = value; }
        }

        /// <summary>
        /// コリジョン検出フラグ
        /// </summary>
        /// <remarks>
        /// コリジョン検出フラグはこのノードをコリジョン検出の対象に含めるかどうかのフラグです。
        /// <c>true</c> の時検出され、<c>false</c> の時検出されません。
        /// 実効コリジョン検出フラグについては、<see cref="Node.IsEffectiveCollisionEnabled"/> を参照してください。
        /// </remarks>
        public bool IsCollisionEnabled {
            get { return collisionEnabled; }
            set { this.collisionEnabled = value; }
        }

        /// <summary>
        /// LOD 処理フラグ
        /// </summary>
        /// <remarks>
        /// LOD 処理フラグはこのノードで LOD 処理を行うかどうかのフラグです。
        /// <c>true</c> の時処理され、<c>false</c> の時処理されません。
        /// このフラグは、このノードに <see cref="LODSelector"/> コンポーネントがアタッチされている時のみ有効です。
        /// LOD の詳細については、<see cref="LODSelector"/> を参照してください。
        /// 実効コリジョン検出フラグについては、<see cref="Node.IsEffectiveLODEnabled"/> を参照してください。
        /// LOD処理が有効でこのフラグが <c>false</c> の時は常に一番詳細度の高いモデルが使用されます。
        /// </remarks>
        /// <see cref="LODSelector"/>
        public bool IsLODEnabled {
            get { return lodEnabled; }
            set { this.lodEnabled = value; }
        }

        /// <summary>
        /// コンポーネントの総数
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされているコンポーネントの総数。
        /// コンポーネントの詳細については、<see cref="Component"/> の解説を参照してください。
        /// </remarks>
        /// <seealso cref="Component"/>
        public int ComponentCount {
            get { return components.Count; }
        }

        /// <summary>
        /// すべてのコンポーネントを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされているすべてのコンポーネントを列挙します。
        /// コンポーネントの詳細については、<see cref="Component"/> の解説を参照してください。
        /// </remarks>
        /// <seealso cref="Component"/>
        public IEnumerable<Component> Components {
            get { return components; }
        }

        /// <inheritdoc/>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (components);
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }
        #endregion


        #region Method

        /// <summary>
        /// コンポーネントのアタッチ
        /// </summary>
        /// <remarks>
        /// このノードに指定のコンポーネント <paramref name="comp"/> をアタッチします。
        /// 空間上の一点に過ぎなかったノードは、コンポーネントをアタッチすることで、種々の機能を持つようになります。
        /// すでにアタッチされているコンポーネントと同一の型のコンポーネントを登録する事はできません｡
        /// （基本型とそこから継承した派生型の２つを登録することは可能です）。
        /// 同一の型とは <seealso cref="System.Object.GetType"/> で取得できる <seealso cref="System.Type"/> を
        /// 等号 <c>==</c> で比較したときに <c>true</c> となる型として定義されます。
        /// なお <see cref="Component"/> オブジェクトは同時に1つの <see cref="Node"/> にのみアタッチ可能です。
        /// 複数のノードにアタッチする事はできません。
        /// </remarks>
        /// <typeparam name="TComp">コンポーネント型</typeparam>
        /// <param name="comp">追加するコンポーネント</param>
        /// <exception cref="ArgumentNullException"><paramref name="comp"/> が <c>null</c></exception>
        /// <exception cref="ArgumentException"><paramref name="comp"/>.<see cref="Component.Node"/> が <c>null</c> でない</exception>
        /// <exception cref="ArgumentException"><paramref name="comp"/>.<see cref="Object.GetType()"/> がアタッチ済みのコンポーネントと同一の時</exception>
        /// <seealso cref="Component"/>
        public void Attach<TComp> (TComp comp) where TComp : Component {
            if (comp == null) {
                throw new ArgumentNullException ("Component is null");
            }
            if (comp.Node != null) {
                throw new ArgumentException ("This component already has node");
            }
            if (components.Any (x => x.GetType () == comp.GetType ())) {
                throw new ArgumentException ("This node has same type of component");
            }

            comp.SetNode ((Node)this);
            this.components.Add (comp);
        }


        /// <summary>
        /// コンポーネントをデタッチします
        /// </summary>
        /// <remarks>
        /// このコンポーネントから指定のコンポーネント <paramref name="comp"/> を削除します。
        /// <paramref name="comp"/> が <c>null</c> またはこのノードにアタッチされていなかった場合は単にこれを無視します。
        /// </remarks>
        /// <param name="comp">削除したいコンポーネント</param>
        /// <returns>削除されたら <c>true</c>, そうでなければ </c>false</c></returns>
        public bool Detach (Component comp) {
            if (comp == null || !components.Contains (comp)) {
                return false;
            }

            comp.SetNode (null);
            return components.Remove (comp);
        }

        /// <summary>
        /// コンポーネントを取得します
        /// </summary>
        /// <remarks>
        /// 指定のインデックス <paramref name="index"/> のコンポーネントを取得します。
        /// </remarks>
        /// <param name="index">コンポーネント番号</param>
        /// <exception cref="IndexOutOfRangeException"><paramref name="index"/> &lt; 0 または <paramref name="index"/> &gt;= <see cref="ComponentCount"/></exception>
        /// <returns>コンポーネント</returns>
        public Component GetComponent (int index) {
            if (index < 0 || index >= ComponentCount) {
                throw new IndexOutOfRangeException ("Index is out of ragne");
            }
            return components[index];
        }

        /// <summary>
        /// 特定の型のコンポーネントを取得します
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされているコンポーネントのうち指定の型 <typeparamref name="TComp"/> のものを返します。
        /// 同じ型のコンポーネントはアタッチできない仕様なので、戻り値のコンポーネントは１つに決定されます。
        /// なお <typeparamref name="TComp"/> に基底クラスを指定した場合は、その基底クラスか、そこから派生するクラスの
        /// コンポーネントが返ります。複数のコンポーネントが該当する場合、どれが返るかは未定義です。
        /// 詳しくは <see cref="Attach{T}"/> の解説を参照してください。
        /// このメソッドは<see cref="As{TComp}"/>の別名です。
        /// </remarks>
        /// <typeparam name="TComp">探しているコンポーネント型</typeparam>
        /// <returns>発見したコンポーネント</returns>
        /// <seealso cref="Attach{T}"/>
        /// <seealso cref="As{TComp}"/>
        public TComp GetComponent<TComp> () where TComp : Component {
            return As<TComp> ();
        }


        /// <summary>
        /// コンポーネント メソッドの呼び出し
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされたすべてのコンポーネントについて指定のメソッド(引数なし)を持っていればそれ呼び出します。
        /// なおこのメソッドは C# のリフレクション機能を使って実装されているので、比較的遅いメソッドです。
        /// <note type="implement">
        /// 名前よくないと思う。せめて <c>InvokeMessage()</c> だろ・・・
        /// </note>
        /// </remarks>
        /// <param name="name">呼び出したいメソッドの名前</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        public void RecvMessage (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Function name is null");
            }

            foreach (var comp in components) {
                var methInfo = comp.GetType ().GetMethod (name);
                if (methInfo != null) {
                    methInfo.Invoke (comp, null);
                }
            }
        }

        /// <summary>
        /// コンポーネント メソッドの呼び出し
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされたすべてのコンポーネントについて指定のメソッド(引数あり)を持っていればそれ呼び出します。
        /// なおこのメソッドは C# のリフレクション機能を使って実装されているので、比較的遅いメソッドです。
        /// <note type="implement">
        /// 名前よくないと思う。せめて <c>InvokeMessage()</c> だろ・・・
        /// </note>
        /// </remarks>
        /// <typeparam name="TArg1">第1引数の型</typeparam>
        /// <param name="name">呼び出したいメソッドの名前</param>
        /// <param name="arg1">第1引数</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        public void RecvMessage<TArg1> (string name, TArg1 arg1) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Function name is null");
            }

            foreach (var comp in components) {
                var m = comp.GetType ().GetMethod (name);
                if (m != null) {
                    var args = new object[] { arg1 };
                    m.Invoke (comp, args);
                }
            }
        }

        /// <summary>
        /// コンポーネント メソッドの呼び出し
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされた指定の型 <typeparamref name="TComp"/> のコンポーネントの中で、
        /// 指定のメソッド(引数なし)を持つものがあればそのメソッドを呼び出します。
        /// なおこのメソッドは C# のリフレクション機能を使って実装されているので、比較的遅いメソッドです。
        /// このメソッドはコンポーネントの型を指定しない <see cref="RecvMessage(string)"/> よりも高速な可能性があります。
        /// <note type="implement">
        /// 名前よくないと思う。せめて <c>InvokeMessage()</c> だろ・・・
        /// </note>
        /// </remarks>
        /// <typeparam name="TComp">コンポーネントの型</typeparam>
        /// <param name="name">呼び出したいメソッドの名前</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        public void RecvMessage<TComp> (string name) where TComp : Component {

            if (name == null || name == "") {
                throw new ArgumentNullException ("Function name is null");
            }

            foreach (var comp in components.Where (c => c is TComp)) {
                var m = comp.GetType ().GetMethod (name);
                if (m != null) {
                    m.Invoke (comp, null);
                }
            }
        }

        /// <summary>
        /// コンポーネント メソッドの呼び出し
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされた指定の型 <typeparamref name="TComp"/> のコンポーネントの中で、
        /// 指定のメソッド(引数あり)を持つものがあればそのメソッドを呼び出します。
        /// なおこのメソッドは C# のリフレクション機能を使って実装されているので、比較的遅いメソッドです。
        /// このメソッドはコンポーネントの型を指定しない <see cref="RecvMessage(string)"/> よりも高速な可能性があります。
        /// <note type="implement">
        /// 名前よくないと思う。せめて <c>InvokeMessage()</c> だろ・・・
        /// </note>
        /// </remarks>
        /// <typeparam name="TComp">コンポーネントの型</typeparam>
        /// <typeparam name="TArg1">第1引数の型</typeparam>
        /// <param name="name">呼び出したいメソッドの名前</param>
        /// <param name="arg1">第1引数</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        public void RecvMessage<TComp, TArg1> (string name, TArg1 arg1) where TComp : Component {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Function name is null");
            }

            foreach (var comp in components.Where (c => c is TComp)) {
                var m = comp.GetType ().GetMethod (name);
                if (m != null) {
                    var args = new object[] { arg1 };
                    m.Invoke (comp, args);
                }
            }
        }

        /// <summary>
        /// コンポーネントがアタッチされているかどうかの判定
        /// </summary>
        /// <remarks>
        /// このノードに指定の型 <typeparamref name="TComp"/> のコンポーネントがアタッチされているかどうかを調べます。
        /// 指定の型か、もしくはその派生型のコンポーネントがアタッチされている場合 <c>true</c> を返します。
        /// </remarks>
        /// <typeparam name="TComp">コンポーネントの型</typeparam>
        /// <returns>アタッチされていれば <c>true</c>, そうでなければ <c>false</c></returns>
        public bool Is<TComp> () where TComp : Component {
            return components.Any (cmp => cmp is TComp);
        }


        /// <summary>
        /// コンポーネントへのキャスト
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされたコンポーネントのうち、指定の型 <typeparamref name="TComp"/> のものを返します。
        /// 存在しなければ <c>null</c> を返します。
        /// 同型のコンポーネントはアタッチできない仕様なので、型が決まればコンポーネントは一意に決まります。
        /// ただし <typeramref name="TComp"> に基底クラスを指定した場合は、そのクラスと派生クラスの中から
        /// どれか1つが返ります。どのコンポーネントが返るかは未定義です。
        /// このメソッドは <see cref="GetComponent{T}"/>() の別名です。
        /// </remarks>
        /// <typeparam name="TComp">コンポーネントの型</typeparam>
        /// <returns>アタッチされていればそのコンポーネント、そうでなければ <c>null</c></returns>
        public TComp As<TComp> () where TComp : Component {
            return (TComp)components.FirstOrDefault (cmp => cmp is TComp);
        }

        /// <summary>
        /// ノードの更新処理
        /// </summary>
        /// <remarks>
        /// このメソッドは、<see cref="IsUpdateEnabled"/> プロパティが <c>true</c> の場合、
        /// このノードにアタッチされたすべてのコンポーネントの <see cref="Component.Update(int)"/> を呼び出し、
        /// コンポーネントを更新します。
        /// <see cref="IsUpdateEnabled"/> プロパティが <c>false</c> の場合は何もしません。
        /// 引数の <paramref name="worldTime"/> には通常現在のワールド時間を指定します。
        /// 各コンポーネントは適切に <see cref="Component.OnUpdate(int)"/> をオーバーライドし、
        /// そのコンポーネントに応じた処理を実装する必要があります。
        /// 更新処理の詳細は <see cref="Component"/> クラスの解説を参照してください。
        /// なおノードの更新順はシーン構造とは関係なく <see cref="ExecutionOrder"/> の順番です。
        /// このメソッドはこのノードのみに作用し、再帰的に子ノードを更新したりはしません。
        /// </remarks>
        /// <param name="worldTime">現在時刻(ワールド時間)</param>
        /// <seealso cref="Component"/>
        public void Update (int worldTime) {
            foreach (var cmp in components) {
                cmp.OnUpdate (worldTime);
            }
        }

        /// <summary>
        /// ノードのLOD選択処理
        /// </summary>
        /// <remarks>
        /// このメソッドは <see cref="IsLODSelectEnabled"/> プロパティが <c>true</c> の場合、
        /// このノードにアタッチされた <see cref="LODSelector"/> コンポーネントの <see cref="LODSelector.OnSelect"/> を呼び出し、
        /// LOD処理を行います。
        /// <see cref="IsLODSelectEnabled"/> プロパティが <c>false</c> の場合は何もしません。
        /// LOD処理の詳細については、<see cref="LODSelector"/> クラスの解説を参照してください。
        /// このノードに <see cref="LODSelector"/> コンポーネントがアタッチされていない場合は何もしません。
        /// このメソッドはこのノードのみに作用し、再帰的に子ノードを更新したりはしません。
        /// </remarks>
        /// <seealso cref="LODSelector"/>
        public void Select () {
            foreach (var s in components.Where (cmp => cmp is LODSelector)) {
                ((LODSelector)s).OnSelect ();
            }
        }

        /// <summary>
        /// ノードのリフレッシュ処理
        /// </summary>
        /// <remarks>
        /// このメソッドは <see cref="IsRefleshEnabled"/> プロパティが <c>true</c> の場合、
        /// このノードにアタッチされたすべてのコンポーネントの <see cref="Component.OnReflesh"/> を呼び出し、
        /// シェーダーに送る <c>Uniform</c> 変数の値を最新の値で書き換えます。
        /// リフレッシュ処理の詳細に対は <c>Component</c> クラスの解説の詳細を参照してください。
        /// <note type="implement">
        /// （重要）：現在 <c>Uniform</c> 変数のリフレッシュの実装は間違えていて、
        /// 毎回リフレッシュなしでプロパティから最新の値を取得している。
        /// 従ってこのメソッドを呼び出しても意味がない（というか中身は空）。
        /// これは実装のほうが間違えている！！
        /// 修正したいが具体的な計画はまだない。
        /// </note>
        /// </remarks>
        public void Reflesh () {
            foreach (var s in components) {
                s.OnReflesh ();
            }
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("Attacable: Components={0}, Exec={1}, Scope={2}, Alpha={3}, Rendering={4}, Picking={5}, Collision={6}, LOD={7}",
                ComponentCount, Scope, Alpha, IsRenderingEnabled, IsPickingEnabled, IsCollisionEnabled, IsLODEnabled);
        }
        #endregion
    }
}
